﻿using System;
using System.IO;
using System.IO.Compression;
using System.ServiceModel.Channels;
using System.Xml;
using CoreEx.Common.Compression;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Wcf.Common.Implementation.Compression
{
    [Implements(typeof(MessageEncoder))]
    public class CustomEncoder : MessageEncoder , IInitialize
    {

        private readonly MessageEncoder _innerEncoder;


        /// <summary>
        /// Initializes a new instance of the <see cref="CustomEncoder"/> class.
        /// </summary>
        /// <param name="innerEncoder">The inner encoder.</param>
        public CustomEncoder(MessageEncoder innerEncoder)
        {
            _innerEncoder = innerEncoder;
        }

        /// <summary>
        /// When overridden in a derived class, reads a message from a specified stream.
        /// </summary>
        /// <returns>
        /// The <see cref="T:System.ServiceModel.Channels.Message"/> that is read from the stream specified.
        /// </returns>
        /// <param name="stream">The <see cref="T:System.IO.Stream"/> object from which the message is read.</param><param name="maxSizeOfHeaders">The maximum size of the headers that can be read from the message.</param><param name="contentType">The Multipurpose Internet Mail Extensions (MIME) message-level content-type.</param>
        public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
        {
            var decompressedStream = new MemoryStream();
            Compressor.Decompress(stream, decompressedStream);
            var message = _innerEncoder.ReadMessage(decompressedStream, maxSizeOfHeaders);
            return message;
        }

        /// <summary>
        /// When overridden in a derived class, reads a message from a specified stream.
        /// </summary>
        /// <returns>
        /// The <see cref="T:System.ServiceModel.Channels.Message"/> that is read from the stream specified.
        /// </returns>
        /// <param name="buffer">A <see cref="T:System.ArraySegment`1"/> of type <see cref="T:System.Byte"/> that provides the buffer from which the message is deserialized.</param><param name="bufferManager">The <see cref="T:System.ServiceModel.Channels.BufferManager"/> that manages the buffer from which the message is deserialized.</param><param name="contentType">The Multipurpose Internet Mail Extensions (MIME) message-level content-type.</param>
        public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
        {
            

            //var compressedStream = new MemoryStream(buffer.Array);

            
            
            //var decompressedStream = new MemoryStream();
            //Compressor.Decompress(compressedStream, decompressedStream);
            //byte[] decompressedBytes = decompressedStream.ToArray();
            //byte[] bufferedBytes = bufferManager.TakeBuffer(decompressedBytes.Length);
            //Array.Copy(decompressedBytes,0,bufferedBytes,0,decompressedBytes.Length);
            //var byteArray = new ArraySegment<byte>(bufferedBytes);
            //bufferManager.ReturnBuffer(buffer.Array);
            //var message = _innerEncoder.ReadMessage(byteArray,bufferManager,contentType);
            //message.Properties.Encoder = this;
            //return message;                        

            //Decompress the buffer
            ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager);
            //Use the inner encoder to decode the decompressed buffer
            Message returnMessage = _innerEncoder.ReadMessage(decompressedBuffer, bufferManager);
            returnMessage.Properties.Encoder = this;
            return returnMessage;
        }


        //Helper method to decompress an array of bytes
        private ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager)
        {
            MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count - buffer.Offset);
            MemoryStream decompressedStream = new MemoryStream();
            int totalRead = 0;
            int blockSize = 1024;
            byte[] tempBuffer = bufferManager.TakeBuffer(blockSize);
            using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress))
            {
                while (true)
                {
                    int bytesRead = gzStream.Read(tempBuffer, 0, blockSize);
                    if (bytesRead == 0)
                        break;
                    decompressedStream.Write(tempBuffer, 0, bytesRead);
                    totalRead += bytesRead;
                }
            }
            bufferManager.ReturnBuffer(tempBuffer);

            byte[] decompressedBytes = decompressedStream.ToArray();
            byte[] bufferManagerBuffer = bufferManager.TakeBuffer(decompressedBytes.Length + buffer.Offset);
            Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);
            Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);

            ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
            bufferManager.ReturnBuffer(buffer.Array);

            return byteArray;
        }


        



        /// <summary>
        /// When overridden in a derived class, writes a message to a specified stream.
        /// </summary>
        /// <param name="message">The <see cref="T:System.ServiceModel.Channels.Message"/> to write to the <paramref name="stream"/>.</param><param name="stream">The <see cref="T:System.IO.Stream"/> object to which the <paramref name="message"/> is written.</param>
        public override void WriteMessage(Message message, Stream stream)
        {
            var compressedStream = Compressor.CreateCompressorStream(stream);
            _innerEncoder.WriteMessage(message,compressedStream);
            stream.Flush();
        }

        /// <summary>
        /// When overridden in a derived class, writes a message of less than a specified size to a byte array buffer at the specified offset.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ArraySegment`1"/> of type byte that provides the buffer to which the message is serialized.
        /// </returns>
        /// <param name="message">The <see cref="T:System.ServiceModel.Channels.Message"/> to write to the message buffer.</param><param name="maxMessageSize">The maximum message size that can be written.</param><param name="bufferManager">The <see cref="T:System.ServiceModel.Channels.BufferManager"/> that manages the buffer to which the message is written.</param><param name="messageOffset">The offset of the segment that begins from the start of the byte array that provides the buffer.</param>
        public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
        {
            //Use the inner encoder to encode a Message into a buffered byte array
            ArraySegment<byte> buffer = _innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
            //Compress the resulting byte array
            return CompressBuffer(buffer, bufferManager, messageOffset);            
        }

        //Helper method to compress an array of bytes
        private ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
        {
            MemoryStream memoryStream = new MemoryStream();
            memoryStream.Write(buffer.Array, 0, messageOffset);

            using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
            {
                gzStream.Write(buffer.Array, messageOffset, buffer.Count);
            }


            byte[] compressedBytes = memoryStream.ToArray();
            byte[] bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length);

            Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length);

            bufferManager.ReturnBuffer(buffer.Array);
            ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, bufferedBytes.Length - messageOffset);

            return byteArray;
        }

        public override string ContentType
        {
            get { return "application/x-gzip"; }
        }

        public override string MediaType
        {
            get { return ContentType; }
        }

        public override MessageVersion MessageVersion
        {
            get { return MessageVersion.Soap12; }
        }

        /// <summary>
        /// Gets or sets the <see cref="ICompressor"/> instance that
        /// is used to compress and decompress messages
        /// </summary>
        public ICompressor Compressor { get; set; }
            


        public void Initialize(IServiceContainer source)
        {
            Compressor = source.GetService<ICompressor>("GZipCompressor");
        }
    }
}